package Strategies;

/*
 Version see below

 Author Lawrence Reid
 */

import java.awt.Color;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IEngine.Type;
import com.dukascopy.api.IOrder.State;

public class SureFireMO implements IStrategy {

	final static String version = new String("1.10.201");
	private IEngine engine = null;
	// private IIndicators indicators = null;
	private IConsole console;
	private IHistory history;
	private IContext context;
	// private ArrayList<IOrder> filledOrderIdList = new ArrayList<IOrder>();
	private IUserInterface userInterface;
	private JTextField labelTickCount;

	private JTextField labelProfitLoss;

	@Configurable("Use tab")
	public boolean useTab = true;

	private int tickCount;
	private int resets;
	private final String tabName = SureFireMO.class.toString();
	private Container container;

	private int tagCounter = 0;
	private SimpleDateFormat sdf_IsValidTradeTime;
	private String parsedStartTime;
	private String parsedEndTime;

	private SimpleDateFormat sdf;

	// Strategy Variables
	@Configurable("Max. Surefire Orders(status==FILLED)")
	public int maxFilledOrders = 3;
	@Configurable("Instrument")
	public Instrument instrument = Instrument.EURUSD;
	@Configurable("Buy or Sell at Startup")
	public IEngine.OrderCommand initialOrderCommand = OrderCommand.BUY;

	@Configurable("initial Lot Size")
	public double initialLotSize = 0.1;
	@Configurable("slippage")
	private double slippage = 1.0;
	public int stopLossPips = 60;
	@Configurable("Take profit")
	public int takeProfitPips = 30;
	@Configurable("Trade start hour GMT")
	public String startTime = "00:00";
	@Configurable("Trade close hour GMT")
	public String endTime = "23:59";

	private IOrder initialOrder = null;
	private IOrder lastHedgeOrder = null;
	private IOrder currentMergedOrder = null;
	private int currentPhase = 0;

	final public String uniqueStrategyName = new String("SureFire V") + version;

	public void onStart(IContext context) throws JFException {
		this.context = context;
		engine = context.getEngine();
		// indicators = context.getIndiOncators();
		console = context.getConsole();
		history = context.getHistory();
		this.userInterface = context.getUserInterface();

		// build either a new tab or a new window
		container = useTab ? buildBottomTab() : buildWindow();

		stopLossPips = 2 * takeProfitPips;

		sdf_IsValidTradeTime = new SimpleDateFormat("HH:mm");
		sdf_IsValidTradeTime.setTimeZone(TimeZone.getTimeZone("GMT"));
		parsedStartTime = startTime.replace(":", "");
		parsedEndTime = endTime.replace(":", "");

		String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
		sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

		switch (this.engine.getType()) {
		case LIVE:
			console.getOut().println(
					"Started: " + this.getClass().toString() + " Version "
							+ version + " Strategy in LIVE environment");
			break;
		case DEMO:
			console.getOut().println(
					"Started: " + this.getClass().toString() + " Version "
							+ version + " Strategy in DEMO environment");
			break;
		case TEST:
			console.getOut().println(
					"Started: " + this.getClass().toString() + " Version "
							+ version + " Strategy in HISTORICAL environment");
			break;

		default:
			break;
		}

	}

	public void onStop() throws JFException {
		this.closeAllOrders();
		// remove tab or dispose the window
		if (useTab) {
			userInterface.removeBottomTab(tabName);
		} else {
			((JFrame) container).dispose();
		}

	}

	public void onTick(Instrument instrument, ITick tick) throws JFException {

		if (labelTickCount != null)
			labelTickCount.setText("ticks since reset: " + tickCount++);

		if (initialOrder != null){
			if (labelProfitLoss != null)
				labelProfitLoss.setText("Initial Order: " + initialOrder.getProfitLossInAccountCurrency());
			return;			
		}
		else if (instrument.equals(this.instrument) && (getCurrentPhase() == 0)
				&& isValidTime(tick.getTime())) {
			initialOrder = placeInitialOrder(instrument);
			if (initialOrder == null) {
				print("Error  onTick mehtod: placeInitialOrder method retuurned null. Stoping everything");
				context.stop();
			}

		}
	}

	public void onBar(Instrument instrument, Period period, IBar askBar,
			IBar bidBar) {
	}

	protected void closeAllOrders() throws JFException {
		closeOrder(this.initialOrder);
		closeOrder(this.currentMergedOrder);
		closeOrder(this.lastHedgeOrder);
	}

	void closeOrder(IOrder order) throws JFException {
		if (order != null)
			if (order.getState() == State.OPENED
					|| order.getState() == State.FILLED)
				order.close();
	}

	protected IOrder placeInitialOrder(Instrument instrument) {
		if (this.initialOrder != null)
			return null;

		// if RSI for the current instrument is over 70 or CCI is over 200 on
		// hourly chart
		// Place a SELL trade with the following values
		// @Price = market price
		// @TP = @Price -
		// SELL Order

		IOrder orderInfo = null;
		double stopLossPrice = 0.0;
		double takeProfitPrice = 0.0;
		double entryPrice = 0.0;

		try {
			orderInfo = engine.submitOrder(getLabel(instrument), instrument,
					initialOrderCommand, initialLotSize, 0, slippage,
					stopLossPrice, takeProfitPrice, 0, uniqueStrategyName);
		} catch (JFException e) {
			print("Error in placeInitialOrder__________________________________________________");
			print("Ordertype: " + initialOrderCommand.toString()
					+ " Order with lotsize:"
					+ (new DecimalFormat("#.####")).format(initialLotSize)
					+ " Entry@:"
					+ (new DecimalFormat("#.#####")).format(entryPrice)
					+ " SL@:"
					+ (new DecimalFormat("#.#####")).format(stopLossPrice)
					+ " TP@:"
					+ (new DecimalFormat("#.#####")).format(takeProfitPrice));
			e.printStackTrace();
			print("____________________________________________________________________________");
		}
		return orderInfo;

	}

	protected Boolean set_SL_TP_OnActualPrice(IOrder order, int stopLossPips,
			int takeProfitPips) {
		/*
		 * First we check if the order is in FILLED status. This we do because
		 * we use the open price
		 */

		double stopLossPrice = 0.0;
		double takeProfitPrice = 0.0;

		if (order.getState() != IOrder.State.FILLED)
			return false;

		instrument = order.getInstrument();

		try {
			stopLossPrice = calculateStopLoss(order.getInstrument(),
					(order.isLong() ? OrderCommand.BUY : OrderCommand.SELL),
					order.getOpenPrice(), stopLossPips);
			takeProfitPrice = calculateTakeProfit(order.getInstrument(),
					(order.isLong() ? OrderCommand.BUY : OrderCommand.SELL),
					order.getOpenPrice(), takeProfitPips);
			order.setStopLossPrice(stopLossPrice,
					(order.isLong() ? OfferSide.BID : OfferSide.ASK));
			order.setTakeProfitPrice(takeProfitPrice);
		} catch (JFException e) {
			print("Error in set_SL_TP_OnActualPrice___________________________________________");
			print("Order Details: "
					+ order.getLabel()
					+ " Ordertype: "
					+ initialOrderCommand.toString()
					+ " Order with lotsize:"
					+ (new DecimalFormat("#.####")).format(initialLotSize)
					+ " Entry@:"
					+ (new DecimalFormat("#.#####")).format(order
							.getOpenPrice()) + " SL@:"
					+ (new DecimalFormat("#.#####")).format(stopLossPrice)
					+ " TP@:"
					+ (new DecimalFormat("#.#####")).format(takeProfitPrice));
			e.printStackTrace();
			print("____________________________________________________________________________");
			return false;
		}
		return true;

	}

	protected IOrder placeHedgeOrder(IOrder unhedgedOrder) {

		if (unhedgedOrder.getState() != State.FILLED) {
			print("ERROR in placing Hedging order because the order<"
					+ unhedgedOrder.toString() + "> is not filled________");
			print("_______________________________________________________________________________________________");
			return null;
		}

		double lotSize = 0.0;

		switch (getCurrentPhase()) {
		case 1:
			// Hedge will be 3 times the initial lot size
			lotSize = 3.0 * initialOrder.getAmount();
			break;
		default:
			lotSize = 2.0 * lastHedgeOrder.getAmount();
			break;
		}

		IEngine.OrderCommand orderCommand;

		if (unhedgedOrder.isLong()) {
			orderCommand = OrderCommand.SELLSTOP; // BID
		} else {
			orderCommand = OrderCommand.BUYSTOP; // ASK
		}

		// IOrder orderInfo = null;
		double stopLossPrice = 0.0;
		double takeProfitPrice = 0.0;
		double entryPrice = 0.0;
		if (orderCommand == OrderCommand.SELLSTOP) {
			// if the initial order was short, we reenter at the same entry
			// price as the initial order
			if (!initialOrder.isLong()) { // Short
				entryPrice = initialOrder.getOpenPrice();
			} else {
				entryPrice = initialOrder.getOpenPrice()
						- (takeProfitPips * instrument.getPipValue());
			}
			stopLossPrice = entryPrice
					+ (stopLossPips * instrument.getPipValue());
			takeProfitPrice = entryPrice
					- (takeProfitPips * instrument.getPipValue());

		} else if (orderCommand == OrderCommand.BUYSTOP) {
			// if the initial order was long, we reenter at the same entry price
			// as the initial order
			if (initialOrder.isLong()) { // Initial order was BUY
				entryPrice = initialOrder.getOpenPrice();
			} else {
				entryPrice = initialOrder.getOpenPrice()
						+ (takeProfitPips * instrument.getPipValue());
			}

			stopLossPrice = entryPrice
					- (stopLossPips * instrument.getPipValue());
			takeProfitPrice = entryPrice
					+ (takeProfitPips * instrument.getPipValue());
		}

		String hedgeLabel = getLabel(instrument);
		print("Hedging with a " + orderCommand.toString() + " " + hedgeLabel
				+ " Order with lotsize:"
				+ (new DecimalFormat("#.####")).format(lotSize) + " Entry@:"
				+ (new DecimalFormat("#.#####")).format(entryPrice) + " SL@:"
				+ (new DecimalFormat("#.#####")).format(stopLossPrice)
				+ " TP@:"
				+ (new DecimalFormat("#.#####")).format(takeProfitPrice));
		try {
			IOrder hedgeOrder = engine.submitOrder(hedgeLabel, instrument,
					orderCommand, lotSize, entryPrice, slippage, stopLossPrice, // SL
					takeProfitPrice, 0, uniqueStrategyName);
			return hedgeOrder;
		} catch (JFException e) {
			print("ERROR in placing Hedging order-----------------------------------------------------------------");
			e.printStackTrace();
			print("_______________________________________________________________________________________________");
			return null;
		} 
	}

	protected synchronized String getLabel(Instrument instrument) {
		String label = instrument.name();
		label = label.substring(0, 2) + label.substring(3, 5);
		label = label + (tagCounter++);
		label = label.toLowerCase();
		return label;
	}

	public void onMessage(IMessage message) throws JFException {
		switch (message.getType()) {
		case ORDERS_MERGE_OK:
			IOrder mergedOrder = message.getOrder();
			print("ORDERS_MERGE_OK " + message.getOrder().getOrderCommand()
					+ " " + message.getOrder().toString());
			if (this.currentMergedOrder == mergedOrder) {
				this.set_SL_TP_OnActualPrice(
						mergedOrder,
						(getCurrentPhase() < maxFilledOrders ? this.stopLossPips
								: this.takeProfitPips), this.takeProfitPips);
				if (getCurrentPhase() < maxFilledOrders) {
					// Hedge the merged order
					this.lastHedgeOrder = this.placeHedgeOrder(mergedOrder);
					if (this.lastHedgeOrder == null) {
						// Take a loss and run
						print("The strategy could not place a hedging order and will now stop execution!!!!!!");
						context.stop();
					}
				} else if (getCurrentPhase() == maxFilledOrders) {
					// now we have hit the maximum amount of filled orders. Here
					// we could try to get out with the minimum amount of loss
					print("Warning: No hedgeing order was placed against the last FILLED order because maximum filled orders has be reached <"
							+ getCurrentPhase() + ">");
					// and if we have more than the max, then shut it all down
				} else if (getCurrentPhase() > maxFilledOrders) {
					print("ERROR MAX PHASE EXCEEDED<" + getCurrentPhase()
							+ "> Stop called");
					context.stop();
				}
			}

			break;
		case ORDER_SUBMIT_REJECTED:
			print("ORDER_SUBMIT_REJECTED " + message.getOrder().toString());
			if (this.initialOrder == message.getOrder()) {
				print("Initial Order could not be placed in phase <"
						+ getCurrentPhase() + "> Stop called. "
						+ message.getContent());

				context.stop();
			}
			break;
		case ORDER_FILL_REJECTED:
			print("ORDER_FILL_REJECTED " + message.getOrder().toString());
			break;
		case ORDER_CLOSE_OK:
			IOrder closedOrder = message.getOrder();
			print("ORDER_CLOSE_OK "
					+ message.getOrder().toString()
					+ " Profit: "
					+ (new DecimalFormat("####.#")).format(closedOrder
							.getProfitLossInPips()));
			if ((closedOrder == initialOrder)
					|| (closedOrder == currentMergedOrder))
				if (closedOrder.getProfitLossInPips() != 0)
					context.stop();
		case ORDER_SUBMIT_OK:
			// print("ORDER_SUBMIT_OK " + message.getOrder().toString());
			// print(message.getOrder());
			break;
		case ORDER_FILL_OK:
			print("ORDER_FILL_OK " + message.getOrder().getOrderCommand() + " "
					+ message.getOrder().toString());
			IOrder filledOrder = message.getOrder();
			// CHeck if the current filled order was the initial order of our
			// strategy

			if (this.initialOrder == filledOrder
					|| this.lastHedgeOrder == filledOrder) {
				/*
				 * if this order was the first order to be filled in this run of
				 * the strategy, then recalculate stopLoss and takeProfit for
				 * this order based on the actual price of execution. All
				 * initial orders are market orders and the SL and TP are the
				 * basis for our strategy.
				 */
				currentPhase++;

				if (this.getCurrentPhase() == 1) {
					this.set_SL_TP_OnActualPrice(filledOrder,
							this.stopLossPips, this.takeProfitPips);
					this.lastHedgeOrder = placeHedgeOrder(filledOrder);
				} else
				// All OK...business as usual
				if (getCurrentPhase() <= maxFilledOrders) {
					if (this.lastHedgeOrder == filledOrder) {
						List<IOrder> positions = new ArrayList<IOrder>();
						if (currentMergedOrder == null)
							positions.add(initialOrder);
						else
							positions.add(currentMergedOrder);
						positions.add(lastHedgeOrder);
						print("Merging orders: " + positions.get(0).toString()
								+ " " + positions.get(1).toString());
						this.currentMergedOrder = this.mergeOrders(
								getLabel(instrument), positions);
						// now we do not hedge this order. This is done after
						// the merge
					}
				}
			} else {
				print("Previous Message was not related to Surefire strategy(external order)");
			}

			break;

		default:
			break;
		}
	}

	public void onAccount(IAccount account) throws JFException {
	}

	/**
	 * @return the number of filled orders during this strategy
	 */
	public int getCurrentPhase() {
		return this.currentPhase;
	}

	public void print(String str) {
		console.getOut().println(str);
	}

	public void print(IOrder order) {
		print("Order Details: "
				+ order.getLabel()
				+ " getCreationTime(): "
				+ sdf.format(order.getCreationTime())
				+ " getInstrument(): "
				+ order.getInstrument()
				+ " getComment(): "
				+ order.getComment()
				+ " getAmount(): "
				+ (new DecimalFormat("#.#####")).format(order.getAmount())
				+ " getOrderCommand(): "
				+ order.getOrderCommand().toString()
				+ " getState(): "
				+ order.getState().toString()
				+ " getStopLossPrice(): "
				+ (new DecimalFormat("#.#####")).format(order
						.getStopLossPrice())
				+ " getTakeProfitPrice(): "
				+ (new DecimalFormat("#.#####")).format(order
						.getTakeProfitPrice()) + " getOpenPrice(): "
				+ (new DecimalFormat("#.#####")).format(order.getOpenPrice()));
	}

	double getSpreadPrice(Instrument instrument) {
		try {
			ITick lastTick = history.getLastTick(instrument);
			double _bid = lastTick.getBid();
			double _ask = lastTick.getAsk();
			// double _spread = (Math.round(_ask * 100000.0d) - Math.round(_bid
			// * 100000.0d)) / 10.0d;
			return Math.abs(_bid - _ask);
		} catch (JFException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1.0;
	}

	private void waitUntilOrderFilledClosedCanceled(IOrder order) {
		int counter = 0;
		while ((!order.getState().equals(IOrder.State.CANCELED) && (counter++ < 4))
				&& !order.getState().equals(IOrder.State.CLOSED)
				&& !order.getState().equals(IOrder.State.FILLED)) {
			order.waitForUpdate(1000);
		}
	}

	protected void waitUntilOrderFilledClosedCanceled(
			ArrayList<IOrder> orderList) throws JFException {
		for (IOrder order : orderList) {
			waitUntilOrderFilledClosedCanceled(order);
		}
	}

	/**
	 * 
	 * @param tickTime
	 *            market tick time in milliseconds
	 * @return
	 */
	private boolean isValidTime(long tickTime) {

		String formattedTickTime = sdf_IsValidTradeTime.format(tickTime);
		formattedTickTime = formattedTickTime.replace(":", "");

		int tickTimeValue = Integer.parseInt(formattedTickTime);
		int startTimeValue = Integer.parseInt(parsedStartTime);
		int endTimeValue = Integer.parseInt(parsedEndTime);

		if (startTimeValue < endTimeValue) {
			if ((tickTimeValue > startTimeValue)
					&& (tickTimeValue < endTimeValue)) {
				return true;
			}
			// Else swap time range and calculate valid time
		} else {
			int tmpTimeValue = startTimeValue;
			startTimeValue = endTimeValue;
			endTimeValue = tmpTimeValue;
			if ((tickTimeValue < startTimeValue)
					|| (tickTimeValue >= endTimeValue)) {
				return true;
			}
		}
		return false;
	}

	public IOrder mergeOrdersSync(String label, List<IOrder> positions)
			throws JFException {

		IOrder rc = null;
		List<IOrder> ordersToMerge = new ArrayList<IOrder>();
		if (positions != null && positions.size() > 1) {
			for (IOrder iOrder : positions) {
				if (iOrder.getState() == IOrder.State.FILLED) {
					// removes SLTP
					if (iOrder.getStopLossPrice() > 0) {
						iOrder.setStopLossPrice(0);
						iOrder.waitForUpdate(8000);
					}
					if (iOrder.getTakeProfitPrice() > 0) {
						iOrder.setTakeProfitPrice(0);
						iOrder.waitForUpdate(8000);
					}
					ordersToMerge.add(iOrder);
				}
			}

			if (ordersToMerge.size() >= 2) {
				IOrder iOrder = engine.mergeOrders(label,
						ordersToMerge.toArray(new IOrder[0]));
				if (iOrder.getState() == IOrder.State.CREATED) {
					iOrder.waitForUpdate(8000);
				}
				rc = iOrder;
			} else if (ordersToMerge.size() == 1) {
				rc = ordersToMerge.get(0);
			}

		}
		return rc;
	}

	public double calculateStopLoss(Instrument currentInstrument,
			OrderCommand cmd, double openPrice, int pips) throws JFException {

		// managing STOP LOSS
		double rc = 0;
		boolean isLong = cmd.isLong();

		if (openPrice <= 0) {
			throw new JFException("Unable to calculate SL for 0 price");
			// openPrice = isLong ?
			// history.getLastTick(currentInstrument).getAsk() :
			// history.getLastTick(currentInstrument).getBid();
		}
		if (isLong) {
			rc = openPrice - (pips * currentInstrument.getPipValue());
		} else {
			rc = openPrice + (pips * currentInstrument.getPipValue());
		}
		rc = round(rc, 5);

		return rc;
	}

	public double calculateTakeProfit(Instrument currentInstrument,
			OrderCommand cmd, double openPrice, int pips) throws JFException {

		// managing TAKE PROFIT
		double rc = 0;
		boolean isLong = cmd.isLong();

		if (openPrice <= 0) {
			throw new JFException("Unable to calculate TP for 0 price");
			// openPrice = isLong ?
			// history.getLastTick(currentInstrument).getAsk() :
			// history.getLastTick(currentInstrument).getBid();
		}

		if (isLong) {
			rc = openPrice + (pips * currentInstrument.getPipValue());
		} else {
			rc = openPrice - (pips * currentInstrument.getPipValue());
		}
		rc = round(rc, 5);

		return rc;
	}

	/**
	 * Rounding function
	 * 
	 * @param value
	 * @param precision
	 * @return - rounded value
	 */
	public double round(double value, int precision) {
		if (Double.isNaN(value)) {
			return value;
		}
		boolean negative = false;
		if (value < 0) {
			negative = true;
			value = -value;
		}
		if (value == 0) {
			return value;
		}
		double multiplier = 1;
		while (precision > 0) {
			multiplier *= 10;
			--precision;
		}
		while (precision < 0 && value * multiplier / 10 >= 1) {
			multiplier /= 10;
			++precision;
		}
		while (value * multiplier < 1) {
			multiplier *= 10;
		}
		value *= multiplier;
		long longValue = (long) (value + 0.5d);
		value = ((double) longValue) / multiplier;
		return negative ? -value : value;
	}

	public IOrder mergeOrders(String label, List<IOrder> positions)
			throws JFException {

		IOrder rc = null;
		List<IOrder> ordersToMerge = new ArrayList<IOrder>();
		if (positions != null && positions.size() > 1) {
			for (IOrder iOrder : positions) {
				if (iOrder.getState() == IOrder.State.FILLED) {
					// removes SLTP
					if (iOrder.getStopLossPrice() > 0) {
						iOrder.setStopLossPrice(0);
						iOrder.waitForUpdate(8000);
					}
					if (iOrder.getTakeProfitPrice() > 0) {
						iOrder.setTakeProfitPrice(0);
						iOrder.waitForUpdate(8000);
					}
					ordersToMerge.add(iOrder);
				}
			}

			if (ordersToMerge.size() >= 2) {
				IOrder iOrder = engine.mergeOrders(label,
						ordersToMerge.toArray(new IOrder[0]));
				rc = iOrder;
			} else if (ordersToMerge.size() == 1) {
				rc = ordersToMerge.get(0);
			}

		}
		return rc;
	}

	private void addComponentsToPane(Container pane) {

		pane.setLayout(null);

		labelTickCount = new JTextField();
		final JButton btn = new JButton("Reset");
		labelProfitLoss = new JTextField();

		pane.add(labelTickCount);
		pane.add(labelProfitLoss);
		pane.add(btn);

		// "Reset" click resets the tick counter and changes the label's color
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				tickCount = 0;
				labelTickCount.setForeground((resets % 2 == 0 ? Color.RED
						: Color.BLUE));
				btn.setText("Reset (" + ++resets + ")");
			}
		});

		// customized size and placement of the label and the button
		Insets insets = pane.getInsets();
		btn.setBounds(25 + insets.left, 5 + insets.top, 100, 25);
		labelTickCount.setBounds(150 + insets.left, 5 + insets.top, 150, 25);
		labelProfitLoss.setBounds(350 + insets.left, 5 + insets.top, 150, 25);

	}

	private JFrame buildWindow() {
		// Create and set up the window.
		JFrame frame = new JFrame(tabName + " window with absolute layout");

		addComponentsToPane(frame);

		// Size and display the window.
		Insets insets = frame.getInsets();
		frame.setSize(400 + insets.left + insets.right, 150 + insets.top
				+ insets.bottom);
		frame.setVisible(true);
		return frame;
	}

	private JPanel buildBottomTab() {
		JPanel tab = userInterface.getBottomTab(tabName);

		addComponentsToPane(tab);
		return tab;
	}
}
